package net.propero.rdp.crypto;

/**
 * This class implements the MD5 message digest algorithm.
 * <p>
 * <b>BUG</b>: The update method is missing.
 * <p>
 * <b>References:</b>
 * <ol>
 * <li> Ronald L. Rivest, "<a href="http://www.roxen.com/rfc/rfc1321.html"> The
 * MD5 Message-Digest Algorithm</a>", IETF RFC-1321 (informational).
 * <li> Bruce Schneier, "Section 18.5 MD5," <cite>Applied Cryptography, 2nd
 * edition</cite>, John Wiley &amp; Sons, 1996
 * <p>
 * </ol>
 * <p>
 * <b>Copyright</b> &copy; 1995-1997 <a
 * href="http://www.systemics.com/">Systemics Ltd</a> on behalf of the <a
 * href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>.
 * <br>
 * All rights reserved.
 * <p>
 * <b>$Revision: 12 $</b>
 * 
 * @author Systemics Ltd
 * @author David Hopwood
 * @since Cryptix 2.2
 */
public final class MD5 extends BlockMessageDigest implements Cloneable {

	// MD5 constants and variables
	// ...........................................................................

	/** Length of the final hash (in bytes). */
	private static final int HASH_LENGTH = 16;

	/** Length of a block (the number of bytes hashed in every transform). */
	private static final int DATA_LENGTH = 64;

	private int[] data;

	private int[] digest;

	private byte[] tmp;

	/** Returns the length of the hash (in bytes). */
	protected int engineGetDigestLength() {
		return HASH_LENGTH;
	}

	/** Returns the length of the data (in bytes) hashed in every transform. */
	protected int engineGetDataLength() {
		return DATA_LENGTH;
	}

	/**
	 * The public constructor.
	 */
	public MD5() {
		super("MD5");
		java_init();
		engineReset();
	}

	private void java_init() {
		digest = new int[HASH_LENGTH / 4];
		data = new int[DATA_LENGTH / 4];
		tmp = new byte[DATA_LENGTH];
	}

	/**
	 * This constructor is here to implement cloneability of this class.
	 */
	private MD5(MD5 md) {
		this();
		data = (int[]) md.data.clone();
		digest = (int[]) md.digest.clone();
		tmp = (byte[]) md.tmp.clone();
	}

	/**
	 * Returns a copy of this MD object.
	 */
	public Object clone() {
		return new MD5(this);
	}

	/**
	 * Initializes (resets) the message digest.
	 */
	public void engineReset() {
		super.engineReset();
		java_reset();
	}

	private void java_reset() {
		digest[0] = 0x67452301;
		digest[1] = 0xEFCDAB89;
		digest[2] = 0x98BADCFE;
		digest[3] = 0x10325476;
	}

	/**
	 * Adds data to the message digest.
	 * 
	 * @param data
	 *            The data to be added.
	 * @param offset
	 *            The start of the data in the array.
	 * @param length
	 *            The amount of data to add.
	 */
	protected void engineTransform(byte[] in) {
		java_transform(in);
	}

	private void java_transform(byte[] in) {
		byte2int(in, 0, data, 0, DATA_LENGTH / 4);
		transform(data);
	}

	/**
	 * Returns the digest of the data added and resets the digest.
	 * 
	 * @return the digest of all the data added to the message digest as a byte
	 *         array.
	 */
	public byte[] engineDigest(byte[] in, int length) {
		byte b[] = java_digest(in, length);
		engineReset();
		return b;
	}

	private byte[] java_digest(byte[] in, int pos) {
		if (pos != 0)
			System.arraycopy(in, 0, tmp, 0, pos);

		tmp[pos++] = -128; // (byte)0x80;

		if (pos > DATA_LENGTH - 8) {
			while (pos < DATA_LENGTH)
				tmp[pos++] = 0;

			byte2int(tmp, 0, data, 0, DATA_LENGTH / 4);
			transform(data);
			pos = 0;
		}

		while (pos < DATA_LENGTH - 8)
			tmp[pos++] = 0;

		byte2int(tmp, 0, data, 0, (DATA_LENGTH / 4) - 2);

		int bc = bitcount();
		data[14] = bc;
		data[15] = 0;

		transform(data);

		byte buf[] = new byte[HASH_LENGTH];

		// Little endian
		int off = 0;
		for (int i = 0; i < HASH_LENGTH / 4; ++i) {
			int d = digest[i];
			buf[off++] = (byte) d;
			buf[off++] = (byte) (d >>> 8);
			buf[off++] = (byte) (d >>> 16);
			buf[off++] = (byte) (d >>> 24);
		}
		return buf;
	}

	// MD5 transform routines
	// ...........................................................................

	static protected int F(int x, int y, int z) {
		return (z ^ (x & (y ^ z)));
	}

	static protected int G(int x, int y, int z) {
		return (y ^ (z & (x ^ y)));
	}

	static protected int H(int x, int y, int z) {
		return (x ^ y ^ z);
	}

	static protected int I(int x, int y, int z) {
		return (y ^ (x | ~z));
	}

	static protected int FF(int a, int b, int c, int d, int k, int s, int t) {
		a += k + t + F(b, c, d);
		a = (a << s | a >>> -s);
		return a + b;
	}

	static protected int GG(int a, int b, int c, int d, int k, int s, int t) {
		a += k + t + G(b, c, d);
		a = (a << s | a >>> -s);
		return a + b;
	}

	static protected int HH(int a, int b, int c, int d, int k, int s, int t) {
		a += k + t + H(b, c, d);
		a = (a << s | a >>> -s);
		return a + b;
	}

	static protected int II(int a, int b, int c, int d, int k, int s, int t) {
		a += k + t + I(b, c, d);
		a = (a << s | a >>> -s);
		return a + b;
	}

	protected void transform(int M[]) {
		int a, b, c, d;

		a = digest[0];
		b = digest[1];
		c = digest[2];
		d = digest[3];

		a = FF(a, b, c, d, M[0], 7, 0xd76aa478);
		d = FF(d, a, b, c, M[1], 12, 0xe8c7b756);
		c = FF(c, d, a, b, M[2], 17, 0x242070db);
		b = FF(b, c, d, a, M[3], 22, 0xc1bdceee);
		a = FF(a, b, c, d, M[4], 7, 0xf57c0faf);
		d = FF(d, a, b, c, M[5], 12, 0x4787c62a);
		c = FF(c, d, a, b, M[6], 17, 0xa8304613);
		b = FF(b, c, d, a, M[7], 22, 0xfd469501);
		a = FF(a, b, c, d, M[8], 7, 0x698098d8);
		d = FF(d, a, b, c, M[9], 12, 0x8b44f7af);
		c = FF(c, d, a, b, M[10], 17, 0xffff5bb1);
		b = FF(b, c, d, a, M[11], 22, 0x895cd7be);
		a = FF(a, b, c, d, M[12], 7, 0x6b901122);
		d = FF(d, a, b, c, M[13], 12, 0xfd987193);
		c = FF(c, d, a, b, M[14], 17, 0xa679438e);
		b = FF(b, c, d, a, M[15], 22, 0x49b40821);

		a = GG(a, b, c, d, M[1], 5, 0xf61e2562);
		d = GG(d, a, b, c, M[6], 9, 0xc040b340);
		c = GG(c, d, a, b, M[11], 14, 0x265e5a51);
		b = GG(b, c, d, a, M[0], 20, 0xe9b6c7aa);
		a = GG(a, b, c, d, M[5], 5, 0xd62f105d);
		d = GG(d, a, b, c, M[10], 9, 0x02441453);
		c = GG(c, d, a, b, M[15], 14, 0xd8a1e681);
		b = GG(b, c, d, a, M[4], 20, 0xe7d3fbc8);
		a = GG(a, b, c, d, M[9], 5, 0x21e1cde6);
		d = GG(d, a, b, c, M[14], 9, 0xc33707d6);
		c = GG(c, d, a, b, M[3], 14, 0xf4d50d87);
		b = GG(b, c, d, a, M[8], 20, 0x455a14ed);
		a = GG(a, b, c, d, M[13], 5, 0xa9e3e905);
		d = GG(d, a, b, c, M[2], 9, 0xfcefa3f8);
		c = GG(c, d, a, b, M[7], 14, 0x676f02d9);
		b = GG(b, c, d, a, M[12], 20, 0x8d2a4c8a);

		a = HH(a, b, c, d, M[5], 4, 0xfffa3942);
		d = HH(d, a, b, c, M[8], 11, 0x8771f681);
		c = HH(c, d, a, b, M[11], 16, 0x6d9d6122);
		b = HH(b, c, d, a, M[14], 23, 0xfde5380c);
		a = HH(a, b, c, d, M[1], 4, 0xa4beea44);
		d = HH(d, a, b, c, M[4], 11, 0x4bdecfa9);
		c = HH(c, d, a, b, M[7], 16, 0xf6bb4b60);
		b = HH(b, c, d, a, M[10], 23, 0xbebfbc70);
		a = HH(a, b, c, d, M[13], 4, 0x289b7ec6);
		d = HH(d, a, b, c, M[0], 11, 0xeaa127fa);
		c = HH(c, d, a, b, M[3], 16, 0xd4ef3085);
		b = HH(b, c, d, a, M[6], 23, 0x04881d05);
		a = HH(a, b, c, d, M[9], 4, 0xd9d4d039);
		d = HH(d, a, b, c, M[12], 11, 0xe6db99e5);
		c = HH(c, d, a, b, M[15], 16, 0x1fa27cf8);
		b = HH(b, c, d, a, M[2], 23, 0xc4ac5665);

		a = II(a, b, c, d, M[0], 6, 0xf4292244);
		d = II(d, a, b, c, M[7], 10, 0x432aff97);
		c = II(c, d, a, b, M[14], 15, 0xab9423a7);
		b = II(b, c, d, a, M[5], 21, 0xfc93a039);
		a = II(a, b, c, d, M[12], 6, 0x655b59c3);
		d = II(d, a, b, c, M[3], 10, 0x8f0ccc92);
		c = II(c, d, a, b, M[10], 15, 0xffeff47d);
		b = II(b, c, d, a, M[1], 21, 0x85845dd1);
		a = II(a, b, c, d, M[8], 6, 0x6fa87e4f);
		d = II(d, a, b, c, M[15], 10, 0xfe2ce6e0);
		c = II(c, d, a, b, M[6], 15, 0xa3014314);
		b = II(b, c, d, a, M[13], 21, 0x4e0811a1);
		a = II(a, b, c, d, M[4], 6, 0xf7537e82);
		d = II(d, a, b, c, M[11], 10, 0xbd3af235);
		c = II(c, d, a, b, M[2], 15, 0x2ad7d2bb);
		b = II(b, c, d, a, M[9], 21, 0xeb86d391);

		digest[0] += a;
		digest[1] += b;
		digest[2] += c;
		digest[3] += d;
	}

	// why was this public?
	// Note: parameter order changed to be consistent with System.arraycopy.
	private static void byte2int(byte[] src, int srcOffset, int[] dst,
			int dstOffset, int length) {
		while (length-- > 0) {
			// Little endian
			dst[dstOffset++] = (src[srcOffset++] & 0xFF)
					| ((src[srcOffset++] & 0xFF) << 8)
					| ((src[srcOffset++] & 0xFF) << 16)
					| ((src[srcOffset++] & 0xFF) << 24);
		}
	}
}
